//
// SnacPacket.cs
//
// Copyright (c) 2007 Lukas Lipka.
//

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

using IcqSharp.Util;

namespace IcqSharp.Packets
{
    public class SnacPacket : FlapPacket
    {
        private ushort family;
        private ushort command;
        private ushort flags;
        private uint reference;

        private MemoryStream data;
        private MemoryStream extra_data;

        private EndianBinaryWriter data_writer;
        private EndianBinaryWriter extra_data_writer;

        public SnacPacket (ushort sequence, ushort family, ushort command, ushort flags, uint reference, byte[] extra_data, byte[] data)
            : base (0x02, sequence, null)
        {
            this.family = family;
            this.command = command;
            this.flags = flags;
            this.reference = reference;

            this.extra_data = extra_data == null ? new MemoryStream () : new MemoryStream (extra_data);
            this.data = data == null ? new MemoryStream () : new MemoryStream (data);

            this.extra_data_writer = new EndianBinaryWriter (EndianBitConverter.Big, this.extra_data);
            this.data_writer = new EndianBinaryWriter (EndianBitConverter.Big, this.data);
        }

        public SnacPacket (ushort family, ushort command, ushort flags, uint reference, byte[] extra_data, byte[] data)
            : base (0x02, null)
        {
            this.family = family;
            this.command = command;
            this.flags = flags;
            this.reference = reference;

            this.extra_data = extra_data == null ? new MemoryStream () : new MemoryStream (extra_data);
            this.data = data == null ? new MemoryStream () : new MemoryStream (data);

            this.extra_data_writer = new EndianBinaryWriter (EndianBitConverter.Big, this.extra_data);
            this.data_writer = new EndianBinaryWriter (EndianBitConverter.Big, this.data);
        }

        public SnacPacket (ushort family, ushort command, uint reference, byte[] extra_data, byte[] data)
            : this (family, command, 0x0000, reference, extra_data, data)
        {
        }

        public SnacPacket (ushort family, ushort command, uint reference)
            : this (family, command, reference, null, null)
        {
        }

        public SnacPacket (ushort family, ushort command)
            : this (family, command, (uint)new Random ().Next (), null, null)
        {
        }

        public new void Add (byte[] value)
        {
            data_writer.Write (value);
        }

        public void AddByte (byte value)
        {
            data_writer.Write (value);
        }

        public void AddWord (ushort value)
        {
            data_writer.Write (value);
        }

        public new void AddDword (UInt32 value)
        {
            data_writer.Write (value);
        }

        public void AddQword (Int64 value)
        {
            data_writer.Write (value);
        }

        public void AddString (string value)
        {
            byte[] bytes = Encoding.UTF8.GetBytes (value);
            Add (bytes);
        }

        public override void AddTlv (Tlv tlv)
        {
            Add (tlv.GetBytes ());
        }

        public override byte[] Write ()
        {
            MemoryStream stream = new MemoryStream ();
            EndianBinaryWriter writer = new EndianBinaryWriter (LittleEndianBitConverter.Big, stream);

            writer.Write ((short)family);
            writer.Write ((short)command);
            writer.Write ((ushort)((extra_data != null && extra_data.Length > 0) ? 0x8000 : 0x0000));
            writer.Write ((int)reference);

            if (extra_data != null && extra_data.Length > 0) {
                writer.Write ((short)extra_data.Length);
                writer.Write (extra_data.ToArray ());
            }

            if (data != null)
                writer.Write (data.ToArray ());

            base.Data = stream.ToArray ();

            stream.Close();

            return base.Write ();
        }

        // Parses given byte array and returns a SnacPacket object
        public static new SnacPacket Parse (byte[] data)
        {
            FlapPacket flap = FlapPacket.Parse (data);

            ushort family = LittleEndianBitConverter.Big.ToUInt16(flap.Data, 0);
            ushort command = LittleEndianBitConverter.Big.ToUInt16(flap.Data, 2);
            ushort flags = LittleEndianBitConverter.Big.ToUInt16(flap.Data, 4);
            uint request = LittleEndianBitConverter.Big.ToUInt16(flap.Data, 6);

            int snac_data_length = flap.Data.Length - 10;

            byte[] extra_data = null;

            // SNAC contains extra data if 0x8000 is
            // in flags
            if (flags == 0x8000) {
                ushort extra_data_length = LittleEndianBitConverter.Big.ToUInt16(flap.Data, 10);                
                extra_data = new byte [extra_data_length];
                Array.Copy(flap.Data, 12, extra_data, 0, extra_data_length);

                snac_data_length -= 2 + extra_data_length;
            }

            byte[] snac_data = new byte [snac_data_length];
            Array.Copy(flap.Data, flap.Data.Length - snac_data_length, snac_data, 0, snac_data_length);

            // Build the actual SnacPacket :-)
            SnacPacket packet = new SnacPacket (flap.Sequence, family, command, flags, request, extra_data, snac_data);

            return packet;
        }

        public ushort Family
        {
            get { return family; }
        }

        public ushort Command
        {
            get { return command; }
        }

        public ushort Flags
        {
            get { return flags; }
        }

        public uint Reference
        {
            get { return reference; }
        }

        public byte[] ExtraData
        {
            get { return extra_data.ToArray (); }
        }

        public override byte[] Data
        {
            get { return this.data.ToArray (); }
        }
    }
}
